Add Azure Connector Namespaces canvas extension 🤖🤖🤖#2250
Open
alexyaang wants to merge 12 commits into
Open
Conversation
A Copilot CLI canvas extension for browsing and adding MCP connectors from an Azure Connector Namespace into a Copilot session. Sign-in is dependency-free (OAuth 2.0 auth-code + PKCE via the Azure CLI public client, loopback redirect); network access is restricted to the public Azure Resource Manager endpoint. MIT licensed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Copilot CLI canvas extension, connector-namespaces, for browsing and connecting Azure “Connector Namespace” MCP connectors, plus companion test/preview harnesses to validate the UX and the end-to-end MCP handshake through the real unwrap proxy.
Changes:
- Adds the
connector-namespacescanvas extension (ARM auth, catalog fetch, install/reauth flows, loopback UI server, MCP unwrap proxy, state persistence). - Adds deterministic regression tests and a standalone preview server to guard recurring UI regressions.
- Adds a standalone MCP smoke-test harness to exercise the real proxy path end-to-end (connect → initialize → tools/list → safe tools/call).
Show a summary per file
| File | Description |
|---|---|
| extensions/connector-namespaces/test/smoke.mjs | Standalone orchestrator to install/connect/probe Microsoft MCP servers and write reports. |
| extensions/connector-namespaces/test/safe-tools.mjs | Heuristic + curated selection of safe read-only tools for automated tools/call. |
| extensions/connector-namespaces/test/README.md | Usage docs for the smoke-test harness and its consent model. |
| extensions/connector-namespaces/test/mcp-probe.mjs | Spawns the real unwrap proxy and drives initialize/tools/list/tools/call over stdio. |
| extensions/connector-namespaces/state.mjs | Persists selected gateway config; tracks session-added connectors. |
| extensions/connector-namespaces/server.test.mjs | Node tests covering CSRF/cross-site gating logic for POST /api/*. |
| extensions/connector-namespaces/server.mjs | Loopback HTTP server providing UI + API routes for the canvas. |
| extensions/connector-namespaces/renderer.test.mjs | Regression tests ensuring spinners aren’t frozen and banner dismiss remains effective. |
| extensions/connector-namespaces/README.md | Extension documentation (prereqs, install, usage, security notes). |
| extensions/connector-namespaces/preview/shots.mjs | Optional agent-browser script to capture visual evidence screenshots. |
| extensions/connector-namespaces/preview/server.mjs | Standalone preview server that stubs /api/* endpoints for deterministic UI states. |
| extensions/connector-namespaces/preview/README.md | Docs for running the preview server and optional screenshot capture. |
| extensions/connector-namespaces/preview/fixtures.mjs | Deterministic fixtures for preview server responses and catalog tiles. |
| extensions/connector-namespaces/preview/.gitignore | Ignores generated preview screenshots. |
| extensions/connector-namespaces/package.json | Declares the extension’s Node module metadata and Copilot SDK dependency. |
| extensions/connector-namespaces/mcp-unwrap-proxy.mjs | Stdio↔HTTP proxy that unwraps Logic Apps $content SSE envelopes into JSON-RPC lines. |
| extensions/connector-namespaces/LICENSE | MIT license for the extension content. |
| extensions/connector-namespaces/install.test.mjs | Unit tests guarding deterministic installed-state selection and safety invariants. |
| extensions/connector-namespaces/install.reauth.test.mjs | Tests ensuring reauth re-consents the existing connection without minting new resources. |
| extensions/connector-namespaces/install.mjs | Core install/reauth flows, ARM helpers, MCP config writer, and installed-state derivation. |
| extensions/connector-namespaces/extension.mjs | Canvas entry point wiring session, actions, and loopback server lifecycle. |
| extensions/connector-namespaces/createPage.mjs | HTML/JS renderer for “Create connector namespace” provisioning wizard page. |
| extensions/connector-namespaces/copilot-extension.json | Minimal extension metadata file (name/version). |
| extensions/connector-namespaces/categories.mjs | Frozen enum defining catalog routing categories (Microsoft vs Partners). |
| extensions/connector-namespaces/catalog.mjs | Fetches managed APIs and filters/categorizes MCP servers for the catalog UI. |
| extensions/connector-namespaces/canvas.json | Canvas metadata (id/name/description/author/screenshots). |
| extensions/connector-namespaces/armClient.mjs | Interactive Azure sign-in + ARM client helpers (paging, host guards, provisioning helpers). |
Review details
- Files reviewed: 28/29 changed files
- Comments generated: 5
- Review effort level: Low
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
Contributor
|
There are quite a few CCR comments on the PR, and several of them are marked as HIGH - can you review them to either action on them (if relevant), or dismiss. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a986299f-86be-46c3-9562-cbf7d25174cf
| // auth-code + PKCE sign-in against it with no app registration of our own. AAD | ||
| // accepts any port on a loopback redirect for public clients, so there's no | ||
| // secret to ship and nothing for a teammate to configure. | ||
| const CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; |
|
|
||
| // In-memory state | ||
| const addedConnectors = new Map(); | ||
| let sessionConfig = null; |
Comment on lines
+535
to
+538
| let result = await armWrite("PUT", url, body, { "If-None-Match": "*" }); | ||
| let state = result?.properties?.provisioningState; | ||
| // ~3 min ceiling (60 * 3s). connectorGateways usually settle in seconds. | ||
| for (let i = 0; i < 60 && state && !TERMINAL_STATES.has(state); i++) { |
Comment on lines
+528
to
+541
| await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); | ||
| const address = server.address(); | ||
| const port = typeof address === "object" && address ? address.port : 0; | ||
| entry.host = `127.0.0.1:${port}`; | ||
| entry.origin = `http://${entry.host}`; | ||
| entry.url = `${entry.origin}/`; | ||
| servers.set(instanceId, entry); | ||
| return entry; | ||
| })(); | ||
| // Record the in-flight start synchronously so a concurrent open() for the | ||
| // same instance awaits this server instead of binding a second one and | ||
| // leaking the first. | ||
| starting.set(instanceId, p); | ||
| p.finally(() => { if (starting.get(instanceId) === p) starting.delete(instanceId); }); |
Comment on lines
+424
to
+426
| for (const connectionName of connectionNames) { | ||
| await armDelete(`${gatewayId(config)}/connections/${armSegment(connectionName)}?api-version=${API_VERSION}`).catch(() => {}); | ||
| } |
Comment on lines
+117
to
+130
| res.writeHead(200, { "Content-Type": "text/html" }); | ||
| res.end( | ||
| "<html><body style=\"font-family:system-ui,sans-serif;padding:2rem\">" + | ||
| "<h2>Signed in to Azure</h2><p>You can close this tab and return to your terminal.</p>" + | ||
| "</body></html>", | ||
| ); | ||
| if (!code || returnedState !== state) { | ||
| finish(reject, new Error("Azure sign-in was cancelled or returned an unexpected response.")); | ||
| return; | ||
| } | ||
| exchangeCode(code, verifier, redirectUri).then( | ||
| (auth) => finish(resolve, auth), | ||
| (err) => finish(reject, err), | ||
| ); |
Comment on lines
+358
to
+359
| <label>Subscription</label> | ||
| <select id="sub-select"> |
| // server: { apiName, displayName, configName } | ||
| // tools: the array returned by tools/list | ||
| // → { tool, args, source } | null (null means "no safe tool, record SKIPPED") | ||
| export function pickSafeTool(server, tools) { |
Comment on lines
+1035
to
+1037
| if (cancelled && pendingConn) { | ||
| await fetch("/api/rollback-connection", { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify({ connName: pendingConn }) }).catch(() => {}); | ||
| } |
Comment on lines
+1096
to
+1099
| const finishUrl = data.configName ? "/api/finish-reauth" : "/api/finish-install"; | ||
| const finishBody = data.configName | ||
| ? { apiName, displayName, connName: data.connName, configName: data.configName, location: data.location, scope: installScope } | ||
| : { apiName, displayName, connName: data.connName, location: data.location, scope: installScope }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Checklist
npm startand verified thatREADME.mdis up to date.mainbranch for this pull request.Description
Adds an Azure Connector Namespaces canvas extension for browsing namespace MCP servers, connecting them to GitHub Copilot, disconnecting local registrations, and opening installed connectors in the Connector Namespace playground.
The extension also exposes the native
connector_namespaces_open_playgroundtool so Copilot can open a named installed connector directly.Screenshots
Connector catalog
Connected MCP controls
Type of Contribution
Additional Notes
Requires an Azure subscription with Connector Namespace resources. Tested with 60 focused extension tests, the standalone preview, the live canvas, and
npm start.I am a Microsoft employee.
By submitting this pull request, I confirm that my contribution abides by the Code of Conduct and will be licensed under the MIT License.